
`include "common_header.verilog"

//  *************************************************************************
//  File : stats_collect_rx_xl
//  *************************************************************************
//  This program is controlled by a written license agreement.
//  Unauthorized Reproduction or Use is Expressly Prohibited.
//  Copyright (c) 2014 MoreThanIP.com, Germany
//  Designed by : Daniel Koehler
//  support@morethanip.com
//  *************************************************************************
//  Description: Implement read/modify/write counters.
//               Resorts and controls the reading of the counters according
//               to the MAC register map.
//  Version    : $Id: stats_collect_rx_xl.v,v 1.6 2017/04/11 09:06:32 gc Exp $
//  *************************************************************************

module stats_collect_rx_xl (

   reset,
   clk,
   stc_clearread,
   stc_saturate,
   stats_rd,
   mreg_sel,
   stats_clear,
   stats_clearvalue,
   stats_busy,
   stats_rdata,
   frm_align_err,
`ifdef STAT_PFCCNT_ENA
   rsv_stat_pfc,
`endif          
   rsv_stat_val,
   rsv_stat
`ifdef MTIPSTAT_MEM_EXTERNAL
   ,
   rstm_raddr,
   rstm_rden,
   rstm_rdata,
   rstm_wren,
   rstm_waddr,
   rstm_wdata
`endif
   );

parameter CNTWIDTH       = 64;          // width of counters
parameter CNTSATURATEOPT = 1;           // implement configurable saturation option (1) or always wrap-around (0)

`ifdef STAT_PFCCNT_ENA
parameter  PFC_PRIORITIES = 8;           //  PFC Priorities, either 8 or 16

localparam CNTNUM        = 32+PFC_PRIORITIES; //  width of final counter vector (one bit per counter)
localparam CNTADDR       = 6;            //  address bits
localparam RAMDEPTH      = 2**CNTADDR;   //  depth of memory storing all counters

`else

localparam CNTNUM        = 32;           //  width of final counter vector (one bit per counter)
localparam CNTADDR       = 5;            //  address bits
localparam RAMDEPTH      = 2**CNTADDR;   //  depth of memory storing all counters

`endif

input   reset;                          //  async active high
input   clk;                            //  line clock
input   stc_clearread;                  //  configuration: clear counter on read
input   stc_saturate;                   //  configuration: counters saturate (1) / wrap around (0)
input   stats_rd;                       //  read request
input   [7:0] mreg_sel;                 //  address in MAC address space
input   stats_clear;                    //  clear all counters
input   [CNTWIDTH-1:0] stats_clearvalue;//  value to write into memory during clear 
output  stats_busy;                     //  statistics read busy
output  [CNTWIDTH - 1:0] stats_rdata;   //  statistics read data return
input   frm_align_err;                  //  Received Frame Aligment Error Indication
`ifdef STAT_PFCCNT_ENA
input   [PFC_PRIORITIES-1:0] rsv_stat_pfc; //  the class enable of latest received PFC frame
`endif
input   rsv_stat_val;                   //  statistic Vector Valid (pulse)
input   [31:0] rsv_stat;                //  receive Statistic Vector

`ifdef MTIPSTAT_MEM_EXTERNAL
output  [CNTADDR-1:0]   rstm_raddr;                     // Stats RX mem Read address (rxclk)
output                  rstm_rden;                      // Stats RX mem read enable (rxclk)
input   [CNTWIDTH+CNTSATURATEOPT-1:0] rstm_rdata;       // Stats RX mem Read data (rxclk)
output                  rstm_wren;                      // Stats RX mem write (rxclk)
output  [CNTADDR-1:0]   rstm_waddr;                     // Stats RX mem write addres (rxclk)
output  [CNTWIDTH+CNTSATURATEOPT-1:0] rstm_wdata;       // Stats RX mem write data (rxclk)

wire    [CNTADDR-1:0]   rstm_raddr;
wire                    rstm_rden; 
wire                    rstm_wren; 
wire    [CNTADDR-1:0]   rstm_waddr;
wire    [CNTWIDTH+CNTSATURATEOPT-1:0] rstm_wdata;
`endif

wire    stats_busy; 
reg     [CNTWIDTH - 1:0] stats_rdata; 



wire    [CNTNUM - 1:0] stat;    //  Statistic Vector one bit per counter
wire    [15:0] stat_octetsok;   //  for octetsOK (payload octets)
wire    [15:0] stat_octets;     //  for etherStatsOctets
wire    stat_val_o;             //  delayed stat_val to align with stat output being valid
wire    [CNTADDR - 1:0] stats_sel; //  address of counter to read (remapped, combinatorial) from mreg_sel
wire    stats_sel_vld;          //  indicate if counter address is valid (one of the herein managed counters has been addressed)
//  Counter vector processing
wire    stat_wait;              //  can be used to delay the start of the burst
wire    [CNTADDR - 1:0] caddr;  //  counter address
wire    caddr_val;              //  counter address valid, should increment
wire    cbusy;                  //  processing is ongoing, asserts following stat_val
wire    vec_busy_int;           //  processing is ongoing, asserts following stat_val, deasserts after cbusy, registered
//  memory
wire    [CNTADDR - 1:0] rad;    //  read address
wire    rden;                   //  read enable, rad is valid and read/modify/write must happen
wire    rden_host;              //  indicate read of host address (omit write)
wire    rden_host_x;            //  indicate read of host address overlaps pipeline read, hence capture current data from pipeline
wire    wclear;                 //  clear counter (write all zero)
wire    [CNTWIDTH + CNTSATURATEOPT - 1:0] dout; 
reg     [CNTADDR - 1:0] wad; 
reg     [CNTWIDTH + CNTSATURATEOPT - 1:0] din; 
reg     wren; 
//  memory pipe
reg     [CNTWIDTH + CNTSATURATEOPT - 1:0] dout_r; //  memory output register
reg     rden_d1; 
reg     rden_d2; 
reg     rden_host_d1; 
reg     rden_host_d2; 
reg     [CNTADDR - 1:0] rad_d1; //  read address
reg     [CNTADDR - 1:0] rad_d2; //  read address
reg     [1:0] sum_sel; //  add octets to the octets counter
wire    [CNTWIDTH:0] x_stats_clearvalue;//  value to write into memory during clear 

assign x_stats_clearvalue = {1'b 0, stats_clearvalue};  // add saturate bit


//  Map statistics vector to a counter vector
//  ------------------------
stats_maprxcnts 
      `ifdef STAT_PFCCNT_ENA
        #(
          .PFC_PRIORITIES(PFC_PRIORITIES)
        )
      `endif          
        U_MAPC (
          .reset(reset),
          .clk(clk),
          .frm_align_err(frm_align_err), //  from MAC
        `ifdef STAT_PFCCNT_ENA
          .rsv_stat_pfc(rsv_stat_pfc),
        `endif          
          .rsv_stat(rsv_stat), //  from MAC
          .rsv_stat_val(rsv_stat_val),
          .stat(stat), //  counter vector
          .stat_octetsok(stat_octetsok),
          .stat_octets(stat_octets),
          .stat_val_o(stat_val_o),
          .vec_busy_int(vec_busy_int),
          .mreg_sel(mreg_sel),
          .stats_sel(stats_sel),
          .stats_sel_vld(stats_sel_vld));

// get consecutive numbers for all bits set in the vector
// ------------------------------------------------------
stats_vectoaddrs #(CNTNUM,CNTADDR) U_V2A (
          .reset(reset),
          .clk(clk),
          .stat_val(stat_val_o),
          .stat(stat),
          .stat_wait(stat_wait),
          .caddr(caddr),
          .caddr_val(caddr_val),
          .cbusy(cbusy),
          .vec_busy_int(vec_busy_int));

// memory read arbitration and clear control
// -----------------------------------------
stats_memarbs #(CNTNUM,CNTADDR) U_MARB (
          .reset(reset),
          .clk(clk),
          .stc_clearread(stc_clearread),
          .stats_rd(stats_rd),
          .stats_sel(stats_sel),
          .stats_sel_vld(stats_sel_vld),
          .stats_clear(stats_clear),
          .stats_busy(stats_busy),
          .stat_wait(stat_wait),
          .vec_busy_int(vec_busy_int),
          .caddr(caddr),
          .caddr_val(caddr_val),
          .rad_d1(rad_d1),
          .rden_d1(rden_d1),
          .rden_d2(rden_d2),
        `ifdef MTIPM10_STAT_NO_READ_PIPE
          .rden_host_d2(rden_host_d1),  // one earlier
        `else 
          .rden_host_d2(rden_host_d2),
        `endif
          .rad(rad),
          .rden(rden),
          .rden_host(rden_host),
          .rden_host_x(rden_host_x),
          .wclear(wclear));

//  ------
//  memory
//  we need to be power of 2 (e.g. instead 28*11 => 32*16=512)
//  ------
`ifdef MTIPSTAT_MEM_EXTERNAL

        assign rstm_raddr = rad;
        assign rstm_rden  = rden;
        assign dout       = rstm_rdata;
        assign rstm_wren  = wren;
        assign rstm_waddr = wad;
        assign rstm_wdata = din; 

`else

sdpm_gen_r #( CNTWIDTH + CNTSATURATEOPT,      // extra overflow bit when saturation option is wanted
              RAMDEPTH,
              CNTADDR) 
        
        U_MEM (
        
        `ifdef SDPM_USE_REGS
        
          .reset_wclk(reset),
          .reset_rclk(reset),
        
        `endif
        
          .wad(wad),
          .din(din),
          .wclk(clk), //  single clock
          .rclk(clk), //  single clock
          .wren(wren),
        `ifdef MTIPSDPM_GEN_READEN
          .rden(rden),
        `endif
          .rad(rad),
          .dout(dout));

`endif

// read/modify/write pipeline
// --------------------------        
always @(posedge reset or posedge clk)
   begin : prmw
   if (reset == 1'b 1)
      begin
      rad_d1    <= {(CNTADDR){1'b 0}};	
      rden_d1   <= 1'b 0;	
      rad_d2    <= {(CNTADDR){1'b 0}};	
      rden_d2   <= 1'b 0;	
      dout_r    <= {(CNTWIDTH + CNTSATURATEOPT){1'b 0}};	
      rden_host_d1 <= 1'b 0;	
      rden_host_d2 <= 1'b 0;	
      sum_sel   <= 2'b 00;	
      wad       <= {(CNTADDR){1'b 0}};	
      din       <= {(CNTWIDTH + CNTSATURATEOPT){1'b 0}};	
      wren      <= 1'b 0;	
      end
   else
      begin

        //  memory output register
      dout_r <= dout;	

        //  align address with memory read output
      rad_d1 <= rad;	
      rad_d2 <= rad_d1;	
      rden_d1 <= rden;	
      rden_d2 <= rden_d1;	
      rden_host_d1 <= rden_host;	
      rden_host_d2 <= rden_host_d1; // extra output hold register required when implementing within line clocks

        //  -----------------
        //  create write data
        //  -----------------
      if (sum_sel == 2'b 01)
         begin
         wad <= rad_d1;	//  write request to clear an entry comes one earlier as usual 
         end
      else
         begin
         wad <= rad_d2;	
         end

        //  determine what we should add
      if (wclear == 1'b 1)
         begin
                //  all counters clear, or clear-on-read from host
         sum_sel <= 2'b 01;	
         end
      else if (rad_d1[CNTADDR - 1:0] == {CNTADDR{1'b 0}} |     // 5'b 00000: etherStatsOctets
               rad_d1[4:0] == 5'b 11111 )     // errorOctets
         begin
                //  add the octets counter, which is read next
         sum_sel <= 2'b 10;	
         end
      else if (rad_d1[CNTADDR - 1:0] == {{(CNTADDR-1){1'b 0}}, 1'b 1} )   // 5'b 00001
         begin
                //  add the octetsOk counter, which is read next
         sum_sel <= 2'b 11;	
         end
      else
         begin
                // normal counter inc by 1
         sum_sel <= 2'b 00;	
         end

        //  create the write data
      case (sum_sel)
      2'b 00:
         begin
         din <= dout_r + {{(CNTWIDTH+CNTSATURATEOPT-1){1'b 0}}, 1'b 1};	//  default inc
         end
      2'b 01:
         begin
         din <= x_stats_clearvalue[CNTWIDTH+CNTSATURATEOPT-1:0];       // {(CNTWIDTH + CNTSATURATEOPT){1'b 0}};	//  clear memory
         end
      2'b 10:
         begin
         din <= dout_r + {{(CNTWIDTH+CNTSATURATEOPT-16){1'b 0}}, stat_octets};	
         end
      2'b 11:
         begin
         din <= dout_r + {{(CNTWIDTH+CNTSATURATEOPT-16){1'b 0}}, stat_octetsok};
         end
      default:  // sanity
         begin
         din <= {(CNTWIDTH + CNTSATURATEOPT){1'b 0}};	
         end
      endcase

        //  control write enable
      if (sum_sel == 2'b 01)
         begin
         wren <= 1'b 1;	//  clear entry
         end
      else if (rden_d2 == 1'b 1 & rden_host_d2 == 1'b 0 )
         begin
         if (CNTSATURATEOPT == 1 & stc_saturate == 1'b 1)
            begin
                //  check for saturated counter:
                //  If it has the MSB set we do no longer write it.
            wren <= ~dout_r[CNTWIDTH+CNTSATURATEOPT-1];	//  do +1-1 to avoid warning if disabled
            end
         else
            begin
            wren <= 1'b 1;	//  write incremented value
            end
         end
      else
         begin
         wren <= 1'b 0;	
         end

      end
   end

//  capture host read data
//  ----------------------
`ifdef MTIPM10_STAT_NO_READ_PIPE

        // no read output holding register
        always @(stc_saturate, dout_r)
        begin
                if (CNTSATURATEOPT == 1 & stc_saturate == 1'b 1 & dout_r[CNTWIDTH + CNTSATURATEOPT - 1] == 1'b 1)
                   begin
                       //  if saturation is enabled and MSB=1 then counter is saturated. Return all 1 data
                   stats_rdata = {(CNTWIDTH){1'b 1}};	
                   end
                else
                   begin
                   stats_rdata = dout_r[CNTWIDTH - 1:0];      // without the overflow bit
                   end
        end           

        // sanity break for invalid configuration
//        `ifndef MTIPM10_STAT_CNT_HCK
//        `include "ERROR_omitting_pipeline_is_possible_only_with_MTIPM10_STAT_CNT_HCK"
//        `endif

`else
        // when implementing in line clock, hold register is required
always @(posedge reset or posedge clk)
   begin : hrd
   if (reset == 1'b 1)
      begin
      stats_rdata <= {(CNTWIDTH){1'b 0}};	
      end
   else
      begin

        //  capture read data from memory and MUST keep it stable

      if (rden_host_d2 == 1'b 1 | rden_host_x == 1'b 1)
         begin
         if (CNTSATURATEOPT == 1 & stc_saturate == 1'b 1 & dout_r[CNTWIDTH + CNTSATURATEOPT - 1] == 1'b 1)
            begin
                //  if saturation is enabled and MSB=1 then counter is saturated. Return all 1 data
            stats_rdata <= {(CNTWIDTH){1'b 1}};	
            end
         else
            begin
            stats_rdata <= dout_r[CNTWIDTH - 1:0];      // without the overflow bit
            end
         end

      end
   end

`endif


endmodule // module stats_collect_rx_xl
